package com.nekonomics
package domain.service


import api.dto.request.{UserLoginReq, UserRegisterReq}
import domain.auxiliary.{AuthResult, UserView}
import domain.entity.User
import domain.validation.UserValidator
import error.UserError.*
import error.{AppError, UserError, ValidationError}
import persistence.repository.UserRepo
import types.UserRole.Buyer
import utils.DoobieUtil.*
import utils.PasswordUtil
import com.nekonomics.domain.service.util.TokenService
import doobie.hikari.HikariTransactor
import zio.*

trait UserService {

  def register(req: UserRegisterReq): DomainIO[Long]

  def queryByUserId(id: Long): DomainIO[UserView]

  def login(req: UserLoginReq): DomainIO[AuthResult]

  def verifyToken(token: String): DomainIO[UserView]
}

object UserService {
  val live: TaskLayer[UserService] = (config.layers ++ TokenService.live ++ persistence.layers) >>> ZLayer.fromFunction(UserServiceLive.apply)

}


case class UserServiceLive(
  xa: HikariTransactor[Task],
  tokenService: TokenService,
  userRepo: UserRepo
) extends UserService {
  override def register(req: UserRegisterReq): DomainIO[Long] = {
    for {
      existing <- userRepo.findByEmail(req.email).convertWithXa(xa)
      res <- existing match {
        case Some(_) => ZIO.fail(AlreadyRegistered)
        case None =>
          for {
            _ <- UserValidator.validate(req.email, req.password, req.nickname)
            passwordHash = PasswordUtil.hash(req.password)
            id <- userRepo.insert(
              email = req.email,
              passwordHash = passwordHash,
              nickname = req.nickname,
              role = Buyer
            ).convertWithXa(xa)
          } yield id
      }
    } yield res
  }


  override def queryByUserId(id: Long): DomainIO[UserView] = {
    for {
      user <- userRepo.findById(id).convertWithXa(xa).someOrFail(UserNotFound)
    } yield UserView.of(user)

  }


  override def login(req: UserLoginReq): DomainIO[AuthResult] = {
    for {
      user <- userRepo.findByEmail(req.email).convertWithXa(xa).someOrFail(UserNotFound)
      _ <- UserValidator.validatePassword(user.passwordHash, req.password)
      token <- tokenService.generateToken(user.id)
    } yield AuthResult(
      token = token,
      user = UserView.of(user)
    )
  }

  override def verifyToken(token: String): DomainIO[UserView] = {
    for {
      userId <- tokenService.verifyToken(token)
      user <- userRepo.findById(userId).convertWithXa(xa).someOrFail(UserNotFound)
    } yield UserView.of(user)
  }
}
